﻿using System;
using System.Collections.Generic;
using System.Text;

namespace AndersLiu.PE
{
    public class MetadataToken
    {
        public static bool IsValidTokenTable(TableIndex tid)
        {
            switch (tid)
            {
                case TableIndex.Module:
                case TableIndex.TypeRef:
                case TableIndex.TypeDef:
                case TableIndex.Field:
                case TableIndex.MethodDef:
                case TableIndex.Param:
                case TableIndex.InterfaceImpl:
                case TableIndex.MemberRef:
                case TableIndex.CustomAttribute:
                case TableIndex.DeclSecurity:
                case TableIndex.StandAloneSig:
                case TableIndex.Event:
                case TableIndex.Property:
                case TableIndex.ModuleRef:
                case TableIndex.TypeSpec:
                case TableIndex.Assembly:
                case TableIndex.AssemblyRef:
                case TableIndex.File:
                case TableIndex.ExportedType:
                case TableIndex.ManifestResource:
                case TableIndex.GenericParam:
                case TableIndex.MethodSpec:
                case TableIndex.GenericParamConstraint:
                case TableIndex.String:
                    return true;
                default:
                    return false;
            }
        }

        public uint Token { get; private set; }
        public TableIndex TableIndex { get; private set; }
        public uint RowIndex { get; private set; }

        public MetadataToken(uint token)
            : this((TableIndex)(token >> 24), token & 0x00FFFFFF)
        {
        }

        public MetadataToken(TableIndex tid, uint rid)
        {
            if (!IsValidTokenTable(tid))
                throw new InvalidTableIndexException();

            if (rid > 0x00FFFFFF)
                throw new InvalidTableRowCountException();

            this.TableIndex = tid;
            this.RowIndex = rid;
            this.Token = ((uint)tid << 24) | rid;
        }

        public override string ToString()
        {
            return string.Format("{0}[{1}]", this.TableIndex, this.RowIndex);
        }
    }

    public class Rid
    {
        public static bool IsValidTable(TableIndex tid)
        {
            return (uint)tid < TableStream.NumberOfTables;
        }

        public TableIndex TableIndex { get; private set; }
        public uint RowIndex { get; private set; }

        public Rid(TableIndex tid, uint rid)
        {
            if (!IsValidTable(tid))
                throw new InvalidTableIndexException();

            this.TableIndex = tid;
            this.RowIndex = rid;
        }

        public override string ToString()
        {
            return string.Format("{0}[{1}]", this.TableIndex, this.RowIndex);
        }
    }

    public class CodedTokenIndex
    {
        public static readonly CodedTokenIndex TypeDefOrRef = new CodedTokenIndex(
            "TypeDefOrRef",
            2,
            new[]
            {
                /* 0 */ TableIndex.TypeDef,
                /* 1 */ TableIndex.TypeRef,
                /* 2 */ TableIndex.TypeSpec,
            });

        public static readonly CodedTokenIndex HasConstant = new CodedTokenIndex(
            "HasConstant",
            2,
            new[]
            {
                /* 0 */ TableIndex.Field,
                /* 1 */ TableIndex.Param,
                /* 2 */ TableIndex.Property,
            });

        public static readonly CodedTokenIndex HasCustomAttribute = new CodedTokenIndex(
            "HasCustomAttribute",
            5,
            new[]
            {
                /*  0 */ TableIndex.MethodDef,
                /*  1 */ TableIndex.Field,
                /*  2 */ TableIndex.TypeRef,
                /*  3 */ TableIndex.TypeDef,
                /*  4 */ TableIndex.Param,
                /*  5 */ TableIndex.InterfaceImpl,
                /*  6 */ TableIndex.MemberRef,
                /*  7 */ TableIndex.Module,
                /*  8 */ TableIndex.DeclSecurity,
                /*  9 */ TableIndex.Property,
                /* 10 */ TableIndex.Event,
                /* 11 */ TableIndex.StandAloneSig,
                /* 12 */ TableIndex.ModuleRef,
                /* 13 */ TableIndex.TypeSpec,
                /* 14 */ TableIndex.Assembly,
                /* 15 */ TableIndex.AssemblyRef,
                /* 16 */ TableIndex.File,
                /* 17 */ TableIndex.ExportedType,
                /* 18 */ TableIndex.ManifestResource,
                /* 19 */ TableIndex.GenericParam,
                /* 20 */ TableIndex.GenericParamConstraint,
                /* 21 */ TableIndex.MethodSpec,
            });

        public static readonly CodedTokenIndex HasFieldMarshall = new CodedTokenIndex(
            "HasFieldMarshall",
            1,
            new[]
            {
                /* 0 */ TableIndex.Field,
                /* 1 */ TableIndex.Param,
            });

        public static readonly CodedTokenIndex HasDeclSecurity = new CodedTokenIndex(
            "HasDeclSecurity",
            2,
            new[]
            {
                /* 0 */ TableIndex.TypeDef,
                /* 1 */ TableIndex.MethodDef,
                /* 2 */ TableIndex.Assembly,
            });

        public static readonly CodedTokenIndex MemberRefParent = new CodedTokenIndex(
            "MemberRefParent",
            3,
            new[]
            {
                /* 0 */ TableIndex.TypeDef,
                /* 1 */ TableIndex.TypeRef,
                /* 2 */ TableIndex.ModuleRef,
                /* 3 */ TableIndex.MethodDef,
                /* 4 */ TableIndex.TypeSpec,
            });

        public static readonly CodedTokenIndex HasSemantics = new CodedTokenIndex(
            "HasSemantics",
            1,
            new[]
            {
                /* 0 */ TableIndex.Event,
                /* 1 */ TableIndex.Property,
            });

        public static readonly CodedTokenIndex MethodDefOrRef = new CodedTokenIndex(
            "MethodDefOrRef",
            1,
            new[]
            {
                /* 0 */ TableIndex.MethodDef,
                /* 1 */ TableIndex.MemberRef,
            });

        public static readonly CodedTokenIndex MemberForwarded = new CodedTokenIndex(
            "MemberForwarded",
            1,
            new[]
            {
                /* 0 */ TableIndex.Field,
                /* 1 */ TableIndex.MethodDef,
            });

        public static readonly CodedTokenIndex Implementation = new CodedTokenIndex(
            "Implementation",
            2,
            new[]
            {
                /* 0 */ TableIndex.File,
                /* 1 */ TableIndex.AssemblyRef,
                /* 2 */ TableIndex.ExportedType,
            });

        public static readonly CodedTokenIndex CustomAttributeType = new CodedTokenIndex(
            "CustomAttributeType",
            3,
            new[]
            {
                /* 0 */ TableIndex.TypeRef,  // obsolete, must not be used
                /* 1 */ TableIndex.TypeDef,  // obsolete, must not be used
                /* 2 */ TableIndex.MethodDef,
                /* 3 */ TableIndex.MemberRef,
                /* 4 */ TableIndex.String,  // obsolete, must not be used
            });

        public static readonly CodedTokenIndex ResolutionScope = new CodedTokenIndex(
            "ResolutionScope",
            2,
            new[]
            {
                /* 0 */ TableIndex.Module,
                /* 1 */ TableIndex.ModuleRef,
                /* 2 */ TableIndex.AssemblyRef,
                /* 3 */ TableIndex.TypeRef,
            });

        public static readonly CodedTokenIndex TypeOrMethodDef = new CodedTokenIndex(
            "TypeOrMethodDef",
            1,
            new[]
            {
                /* 0 */ TableIndex.TypeDef,
                /* 1 */ TableIndex.MethodDef,
            });

        public string Name { get; private set; }

        internal int TagSize { get; private set; }
        internal TableIndex[] ReferencedTables { get; private set; }

        internal TableIndex GetTable(uint codedToken)
        {
            var tid = codedToken & ((uint)(1 << this.TagSize) - 1);

            if (tid >= this.ReferencedTables.Length)
                throw new NotSupportedException();

            return this.ReferencedTables[tid];
        }

        internal uint GetRowId(uint codedToken)
        {
            return codedToken >> this.TagSize;
        }

        private CodedTokenIndex(string name, int tagSize, TableIndex[] tables)
        {
            this.Name = name;
            this.TagSize = tagSize;
            this.ReferencedTables = tables;
        }
    }

    public class CodedToken
    {
        public TableIndex TableIndex { get; private set; }
        public uint RowIndex { get; private set; }
        public CodedTokenIndex CodedTokenIndex { get; private set; }

        public CodedToken(CodedTokenIndex ctid, uint codedToken)
        {
            this.CodedTokenIndex = ctid;
            this.TableIndex = ctid.GetTable(codedToken);
            this.RowIndex = ctid.GetRowId(codedToken);
        }

        public override string ToString()
        {
            return string.Format("{0}[{1}]", this.TableIndex, this.RowIndex);
        }
    }
}
